package view;

import java.awt.Font;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;

import locsi.UserSettings;
import model.LogicType;
import model.simulationmodel.LogicValue;

/**
 * Utility-Klasse <br>
 * <b>Funktion:</b> Dient zur zentralen Sammlung aller benoetigten Designs <br>
 * <br>
 * Diese Klasse erzeugt bei Aufruf der <code>getLogicDesign()</code> Methode die
 * grafische Darstellung des uebergebenen <code>LogicType</code>. <br>
 * 
 * @author Team Locsi
 * 
 */
public final class UtilityLogicDesigns {

	private static FontRenderContext frc = new FontRenderContext(null, true,
			true);

	// Erzeugen von Schriftarten
	private static Font FONT_STD = new Font(Font.MONOSPACED, Font.PLAIN, 14);
	private static Font FONT_2MX = new Font("Arial", Font.BOLD, 18);

	// Breite eines selbstdefinierten Bauteils
	public static final double SD_WIDTH = 100;

	private static String FF_NQ = "\\Q";
	private static String FF_Q = "Q";
	private static String FF_SET = "S";
	private static String FF_RESET = "R";
	private static String TWO_MX_MIDDLE = "2MX";
	private static String TWO_MX_SET = "s";

	/**
	 * Privater Konstruktor, da diese Klasse eine Utility-Klasse darstellt und
	 * somit kein Objekt dieser Klasse erzeugt werden soll/kann.
	 */
	private UtilityLogicDesigns() {
	}

	/**
	 * Erzeugt einen <code>GeneralPath</code> der das Aussehen des Bauteils mit
	 * dem uebergebenen Typ darstellt.
	 * 
	 * @param type
	 *            <code>LogicType</code> - Typ des Bauteils fuerr dass ein
	 *            GeneralPath benoetigt wird
	 * @param xPos
	 *            <code>double</code> - X-Koordinate des Bauteils in Pixel
	 * @param yPos
	 *            <code>double</code> - Y-Koordinate des Bauteils in Pixel
	 * @return <code>Shape</code> - das Aussehen des Bauteils als
	 *         <code>GeneralPath</code>
	 */
	public static Shape getLogicDesign(LogicType type, double xPos, double yPos) {
		// Methode die an Hand des uebergebenen Typs und den dazugehoerigen
		// Koordinaten die entspechenden Designs erzeugt und diese als
		// GeneralPath zurueck gibt

		GeneralPath path = new GeneralPath();
		switch (type) {

		case AND:
			path.moveTo(xPos, yPos + 50);
			path.lineTo(xPos + 10, yPos + 50);
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos - 16,
					yPos, 50, 50), 270, 180, Arc2D.OPEN), false);
			path.lineTo(xPos, yPos);
			path.lineTo(xPos, yPos + 50);
			return path;

		case OR:
			path.moveTo(xPos, yPos + 50);
			path.lineTo(xPos + 10, yPos + 50);
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos - 16,
					yPos, 50, 50), 270, 180, Arc2D.OPEN), false);
			path.lineTo(xPos, yPos);
			path.quadTo(xPos + 16, yPos + 26, xPos, yPos + 50);
			return path;

		case NOT:
			path.moveTo(xPos + 40, yPos + 25);
			path.lineTo(xPos, yPos);
			path.lineTo(xPos, yPos + 50);
			path.lineTo(xPos + 40, yPos + 25);
			path.append(new Ellipse2D.Double(xPos + 41, yPos + 21, 6, 6), false);
			return path;

		case XOR:
			path.moveTo(xPos, yPos + 50);
			path.lineTo(xPos + 10, yPos + 50);
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos - 16,
					yPos, 50, 50), 270, 180, Arc2D.OPEN), false);
			path.lineTo(xPos, yPos);
			path.quadTo(xPos + 16, yPos + 26, xPos, yPos + 50);
			path.moveTo(xPos - 5, yPos);
			path.quadTo(xPos + 11, yPos + 26, xPos - 5, yPos + 50);
			return path;

		case NAND:
			path.moveTo(xPos, yPos + 50);
			path.lineTo(xPos + 10, yPos + 50);
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos - 16,
					yPos, 50, 50), 270, 180, Arc2D.OPEN), false);
			path.lineTo(xPos, yPos);
			path.lineTo(xPos, yPos + 50);
			path.moveTo(xPos + 34, yPos + 50);
			path.append(new Ellipse2D.Double(xPos + 35, yPos + 21, 6, 6), false);
			return path;

		case TWO_MX:
			path.append(new Rectangle2D.Double(xPos, yPos, 70, 50), true);
			path.append(FONT_2MX.createGlyphVector(frc, TWO_MX_MIDDLE)
					.getOutline((float) xPos + 21, (float) yPos + 31), false);
			path.append(
					FONT_STD.createGlyphVector(frc, TWO_MX_SET).getOutline(
							(float) xPos + 5, (float) yPos + 37), false);
			return path;

		case FLIPFLOP:
			path.append(new Rectangle2D.Double(xPos, yPos, 60, 80), true);
			path.append(
					FONT_STD.createGlyphVector(frc, FF_SET).getOutline(
							(float) xPos + 14, (float) yPos + 18), false);
			path.append(
					FONT_STD.createGlyphVector(frc, FF_RESET).getOutline(
							(float) xPos + 14, (float) yPos + 68), false);
			path.append(
					FONT_STD.createGlyphVector(frc, FF_Q).getOutline(
							(float) xPos + 38, (float) yPos + 18), false);
			path.append(
					FONT_STD.createGlyphVector(frc, FF_NQ).getOutline(
							(float) xPos + 30, (float) yPos + 68), false);
			return path;

		case NODE:
			return new Area(new Ellipse2D.Double(xPos, yPos, 11, 11));

		case LAMP:
			if (UserSettings.uglyLamp) {
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos, yPos, 31,
					45), 0, 360, Arc2D.OPEN), false);
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos + 12,
					yPos + 48, UtilityConstants.IO_RADIUS - 1,
					UtilityConstants.IO_RADIUS - 1), 0, 360, Arc2D.OPEN), false);
			} else {
				path.append(new Arc2D.Double(new Rectangle2D.Double(xPos + 4, yPos + 23, 23,
						23), 0, 360, Arc2D.OPEN), false);
				path.append(new Arc2D.Double(new Rectangle2D.Double(xPos + 12,
						yPos + 48, UtilityConstants.IO_RADIUS - 1,
						UtilityConstants.IO_RADIUS - 1), 0, 360, Arc2D.OPEN), false);
			}
			return path;

		case CLOCK:
			path.append(new Rectangle2D.Double(xPos, yPos, 30, 30), false);
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos + 32,
					yPos + 11, UtilityConstants.IO_RADIUS - 1,
					UtilityConstants.IO_RADIUS - 1), 0, 360, Arc2D.OPEN), false);
			return path;

		case SWITCH:
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos, yPos, 23,
					23), 0, 360, Arc2D.OPEN), false);
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos + 26,
					yPos + 7, UtilityConstants.IO_RADIUS - 1,
					UtilityConstants.IO_RADIUS - 1), 0, 360, Arc2D.OPEN), false);
			return path;

		default:
			path.append(new Rectangle2D.Double(xPos, yPos, 100, 60), false);
			return path;
		}
	}

	/**
	 * Diese Methode erzeugt ein Shape, dessen Hoehe vom Maximum aus Anzahl der
	 * Inputs und Outputs abhaengig ist
	 * 
	 * @param xPos
	 *            <code>double</code> X-Koordinate des Bauteils in Pixel
	 * @param yPos
	 *            <code>double</code> Y-Koordinate des Bauteils in Pixel
	 * @param maxPorts
	 *            <code>int</code> Maximum aus der Anzahl der Inputs und Outputs
	 *            eines selbstdefinierten Bauteils
	 * @return
	 */
	public static Shape getLogicSelfdefinedDesign(double xPos, double yPos,
			int maxPorts) {
		GeneralPath path = new GeneralPath();
		path.append(
				new Rectangle2D.Double(xPos, yPos, 100, maxPorts
						* UtilityConstants.IO_MARGIN + 20
						+ UtilityConstants.IO_RADIUS), false);
		return path;
	}

	/**
	 * Gibt je nach Belegung die entsprechende Shape des Taktgebers zurueck -
	 * Wichtig fuer den Simulationsmodus
	 * 
	 * @param xPos
	 *            <code>double</code> xPosition des Taktgebers
	 * @param yPos
	 *            <code>double</code> yPosition des Taktgebers
	 * @param value
	 *            <code>LogicValue</code> Logischer Wert
	 * @return passende Shape
	 *         <code>Shape</Shape> - Aussehen des Taktgebers in Abhaengigkeit dessen Belegung
	 */
	public static Shape getClockDesign(double xPos, double yPos,
			LogicValue value) {
		GeneralPath path = new GeneralPath();

		// logisch 1
		if (value == LogicValue.L1) {
			path.append(new Rectangle2D.Double(xPos, yPos, 30, 30), false);
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos + 32,
					yPos + 11, UtilityConstants.IO_RADIUS,
					UtilityConstants.IO_RADIUS), 0, 360, Arc2D.OPEN), false);
			path.append(new Line2D.Double(xPos + 15, yPos + 7, xPos + 15,
					yPos + 23), false);
			// logisch 0
		} else if (value == LogicValue.L0) {
			path.append(new Rectangle2D.Double(xPos, yPos, 30, 30), false);
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos + 32,
					yPos + 11, UtilityConstants.IO_RADIUS,
					UtilityConstants.IO_RADIUS), 0, 360, Arc2D.OPEN), false);
			path.append(new Arc2D.Double(xPos + 7, yPos + 7, 16, 16, 0, 360,
					Arc2D.OPEN), false);

			// logisch undefiniert
		} else if (value == LogicValue.UNDEFINED) {
			path.append(new Rectangle2D.Double(xPos, yPos, 30, 30), false);
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos + 32,
					yPos + 11, UtilityConstants.IO_RADIUS,
					UtilityConstants.IO_RADIUS), 0, 360, Arc2D.OPEN), false);
		}
		return path;
	}

	/**
	 * Gibt je nach Belegung die entsprechende Shape des Schalters zurueck -
	 * Wichtig fuer den Simulationsmodus
	 * 
	 * @param xPos
	 *            <code>double</code> - xPosition des Schalters in Pixel
	 * @param yPos
	 *            <code>double</code> - yPosition des Schalters in Pixel
	 * @param value
	 *            <code>LogicValue</code> - Logischer Wert
	 * @return <code>Shape</code> - Shape des Schalters in Abhaengigkeit seiner
	 *         Belegung
	 */
	public static Shape getSimulationSwithDesign(double xPos, double yPos,
			LogicValue value) {
		GeneralPath path = new GeneralPath();

		// logisch 1
		if (value == LogicValue.L1) {
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos, yPos, 23,
					23), 0, 360, Arc2D.OPEN), false);
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos + 26,
					yPos + 7, UtilityConstants.IO_RADIUS,
					UtilityConstants.IO_RADIUS), 0, 360, Arc2D.OPEN), false);
			path.append(new Line2D.Double(xPos + 11.5, yPos + 6, xPos + 11.5,
					yPos + 17), false);
			return path;

			// logisch 0
		} else if (value == LogicValue.L0) {
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos, yPos, 23,
					23), 0, 360, Arc2D.OPEN), false);
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos + 26,
					yPos + 7, UtilityConstants.IO_RADIUS,
					UtilityConstants.IO_RADIUS), 0, 360, Arc2D.OPEN), false);
			path.append(new Arc2D.Double(xPos + 6, yPos + 6, 11, 11, 0, 360,
					Arc2D.OPEN), false);
			return path;

			// logisch undefiniert
		} else if (value == LogicValue.UNDEFINED) {
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos, yPos, 23,
					23), 0, 360, Arc2D.OPEN), false);
			path.append(new Arc2D.Double(new Rectangle2D.Double(xPos + 26,
					yPos + 7, UtilityConstants.IO_RADIUS,
					UtilityConstants.IO_RADIUS), 0, 360, Arc2D.OPEN), false);
			return path;
		}
		return null;
	}
}
